/***************************************************************************
 * Copyright 2012-2013 TXT e-solutions SpA
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * This work was performed within the IoT_at_Work Project
 * and partially funded by the European Commission's
 * 7th Framework Programme under the contract ICT-257367.
 *
 * Authors:
 *      Cristoforo Seccia
 *
 * Contributors:
 *        Domenico Rotondi (TXT e-solutions SpA)
 **************************************************************************/
package it.txt.ens.schema.response.factory;

import it.txt.access.capability.commons.identifier.IdentifierGenerator;
import it.txt.access.capability.commons.schema.factory.CapabilitySchemasJaxbFaxtory;
import it.txt.access.capability.commons.utils.CalendarUtils;
import it.txt.access.capability.commons.utils.DOMUtils;
import it.txt.ens.schema.FailureResponseType;
import it.txt.ens.schema.ObjectFactory;
import it.txt.ens.schema.ResponseType;
import it.txt.ens.schema.SubjectType;
import it.txt.ens.schema.SuccessResponseType;
import it.txt.ens.schema.factory.ENSResponseFactoryException;
import it.txt.ens.schema.factory.NamespaceMapper;
import it.txt.ens.schema.factory.SchemasLoader;
import it.txt.ens.schema.factory.model.FailureResponseDetails;
import it.txt.ens.schema.factory.model.SuccessResponseDetails;

import java.io.InputStream;
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.util.Date;

import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Source;

import org.w3c.dom.Document;
import org.xml.sax.SAXException;

/**
 * Implements the interface {@link ENSAuthorisationResponseFactory}.
 * @author Cristoforo Seccia (TXT e-solutions SpA)
 */
public class ENSAuthorisationResponseFactoryImpl implements ENSAuthorisationResponseFactory {

//    private static final Logger LOGGER = Logger.getLogger(ENSAuthorisationResponseFactoryImpl.class.getName());
    private final CapabilitySchemasJaxbFaxtory wrapper;
    private static ENSAuthorisationResponseFactory factory;
    private final ObjectFactory ensResponseFactory;
    private final String capabilitySchemasRootPath;

    private ENSAuthorisationResponseFactoryImpl(String capabilitySchemasRootPath) throws ENSResponseFactoryException {
        try {
            SchemasLoader loader = new SchemasLoader();
            wrapper = new CapabilitySchemasJaxbFaxtory(
                    loader.loadENSServiceCapabilityEntities(capabilitySchemasRootPath),
                    new NamespaceMapper(), ResponseType.class);
            ensResponseFactory = new ObjectFactory();
            this.capabilitySchemasRootPath = capabilitySchemasRootPath;
        } catch (JAXBException ex) {
            throw new ENSResponseFactoryException(ex.getMessage(), ex);
        } catch (SAXException ex) {
            throw new ENSResponseFactoryException(ex.getMessage(), ex);
        }
    }

    public synchronized static ENSAuthorisationResponseFactory getInstance(String capabilitySchemasRootPath)
            throws ENSResponseFactoryException {
        if (factory == null) {
            factory = new ENSAuthorisationResponseFactoryImpl(capabilitySchemasRootPath);
        } else if (!factory.getSchemasRootPath().equals(capabilitySchemasRootPath))
            factory = new ENSAuthorisationResponseFactoryImpl(capabilitySchemasRootPath);
        return factory;
    }

    /**
     * Warning! Use this method only if the CapabilitySchemaFactory has been
     * initialized with one of the parameterized 'getInstance(...)' method.
     */
    public synchronized static ENSAuthorisationResponseFactory getInstance() throws ENSResponseFactoryException {
        if (factory == null) {
            throw new ENSResponseFactoryException("ENSServiceResponseFactory has not been initialized");
        }
        return factory;
    }

    /* (non-Javadoc)
     * @see it.txt.ens.schema.response.factory.ENSAuthorisationResponseFactory#getENSServiceResponseFromSource(javax.xml.transform.Source)
     */
    @Override
    public synchronized ResponseType unmarshal(Source source) throws ENSResponseFactoryException {
        try {
            return (ResponseType) wrapper.getUnmarshaller().unmarshal(source);
        } catch (JAXBException ex) {
            throw new ENSResponseFactoryException(ex.getMessage(), ex);
        }
    }

    /* (non-Javadoc)
     * @see it.txt.ens.schema.response.factory.ENSAuthorisationResponseFactory#getENSServiceResponseFromInputStream(java.io.InputStream)
     */
    @Override
    public synchronized ResponseType parseInputStream(InputStream inputStream) throws ENSResponseFactoryException {
        try {
            return (ResponseType) wrapper.getUnmarshaller().unmarshal(inputStream);
        } catch (JAXBException ex) {
            throw new ENSResponseFactoryException(ex.getMessage(), ex);
        }
    }

    /* (non-Javadoc)
     * @see it.txt.ens.schema.response.factory.ENSAuthorisationResponseFactory#getENSServiceResponseToDocument(it.txt.ens.schema.ResponseType)
     */
    @Override
    public synchronized Document marshal(ResponseType response) throws ENSResponseFactoryException {
        return getENSServiceResponseToDocument(response, Boolean.FALSE);
    }

    /* (non-Javadoc)
     * @see it.txt.ens.schema.response.factory.ENSAuthorisationResponseFactory#getFragmentENSServiceResponseToDocument(it.txt.ens.schema.ResponseType)
     */
    @Override
    public synchronized Document marshalIntoFragment(ResponseType response) throws ENSResponseFactoryException {
        return getENSServiceResponseToDocument(response, Boolean.TRUE);
    }

    private synchronized Document getENSServiceResponseToDocument(ResponseType response, boolean fragment) throws ENSResponseFactoryException {
        try {
            Document responseDocument = DOMUtils.newDocument();
            wrapper.getMarshaller().setProperty(Marshaller.JAXB_FRAGMENT, fragment);
            wrapper.getMarshaller().marshal(response, responseDocument);
            return responseDocument;
        } catch (ParserConfigurationException ex) {
            throw new ENSResponseFactoryException(ex.getMessage(), ex);
        } catch (JAXBException ex) {
            throw new ENSResponseFactoryException(ex.getMessage(), ex);
        }
    }

    private synchronized ResponseType createENSServiceBasicResponse(String requestID,
            XMLGregorianCalendar requestTimeStamp) throws ENSResponseFactoryException {

        ResponseType responseType = ensResponseFactory.createResponseType();
        responseType.setVersion(String.valueOf(ENSSERVICE_RESPONSE_VERSION));
        //Set the response time stamp
        Date now = new Date();
        XMLGregorianCalendar issueIstant = null;
        try {
            issueIstant = CalendarUtils.newXmlGregorianCalendarFromDateAndTime(now, now);
        } catch (DatatypeConfigurationException ex) {
            throw new ENSResponseFactoryException("Error while creating TimeStamp.", ex);
        }
        XMLGregorianCalendar issueIstantUTC = issueIstant.normalize();
        responseType.setTimeStamp(issueIstantUTC);
        //Set the Response ID
        try {
            responseType.setResponseID(IdentifierGenerator.generateIdentifier());
        } catch (NoSuchAlgorithmException ex) {
            throw new ENSResponseFactoryException("Error while generating identifier.", ex);
        }
        //set the request id
        responseType.setRequestID(requestID);
        responseType.setRequestTimeStamp(requestTimeStamp);
        return responseType;
    }

    /* (non-Javadoc)
     * @see it.txt.ens.schema.response.factory.ENSAuthorisationResponseFactory#createENSServiceDSuccessResponse(java.lang.String, javax.xml.datatype.XMLGregorianCalendar, it.txt.ens.schema.factory.model.SuccessResponseDetails)
     */
    @Override
    public synchronized ResponseType createSuccessResponse(String requestID,
            XMLGregorianCalendar requestTimeStamp, SuccessResponseDetails details)
            throws ENSResponseFactoryException {
        ResponseType responseType =
                createENSServiceBasicResponse(requestID, requestTimeStamp);
        //set the failure details
        ResponseType.ResultDetails resultDetails =
                ensResponseFactory.createResponseTypeResultDetails();
        SuccessResponseType successResponseType =
                ensResponseFactory.createSuccessResponseType();
        SubjectType subjectType = ensResponseFactory.createSubjectType();
        subjectType.setSubjectID(details.getSubjectID());
        successResponseType.setSubject(subjectType);
        successResponseType.setAccessToken(details.getAccessToken());
        successResponseType.setQueueName(details.getQueueName());
        BigInteger bigPort = BigInteger.valueOf(details.getBrokerPort());
        successResponseType.setBrokerPort(bigPort);
        successResponseType.setBrokerHost(details.getBrokerHost());
        successResponseType.setVirtualHost(details.getVirtualHost());
        successResponseType.setUseTLS(details.isUseTLS());
        successResponseType.setSessionToken(details.getSesionToken());
        XMLGregorianCalendar expirationXmlgc = null;
        try {
            expirationXmlgc = CalendarUtils.newXmlGregorianCalendarFromDateAndTime(
                    details.getSessionExpiration(), details.getSessionExpiration());
        } catch (DatatypeConfigurationException ex) {
            throw new ENSResponseFactoryException("Error while creating TimeStamp.", ex);
        }
        XMLGregorianCalendar expirationXmlgcUTC = expirationXmlgc.normalize();
        successResponseType.setSessionExpiration(expirationXmlgcUTC);
        resultDetails.setSuccessResponse(successResponseType);
        responseType.setResult(true);
        responseType.setResultDetails(resultDetails);
        return responseType;
    }

    /* (non-Javadoc)
     * @see it.txt.ens.schema.response.factory.ENSAuthorisationResponseFactory#createENSServiceDFailureResponse(java.lang.String, javax.xml.datatype.XMLGregorianCalendar, it.txt.ens.schema.factory.model.FailureResponseDetails)
     */
    @Override
    public synchronized ResponseType createFailureResponse(String requestID,
            XMLGregorianCalendar requestTimeStamp, FailureResponseDetails details)
            throws ENSResponseFactoryException {
        ResponseType responseType =
                createENSServiceBasicResponse(requestID, requestTimeStamp);
        //set the failure details
        ResponseType.ResultDetails resultDetails =
                ensResponseFactory.createResponseTypeResultDetails();
        FailureResponseType failureResponseType =
                ensResponseFactory.createFailureResponseType();
        failureResponseType.setErrorCode(details.getErrorCode());
        failureResponseType.setErrorReason(details.getErrorReason());
        resultDetails.setFailureResponse(failureResponseType);
        responseType.setResult(false);
        responseType.setResultDetails(resultDetails);
        return responseType;
    }

    /* (non-Javadoc)
     * @see it.txt.ens.schema.response.factory.ENSAuthorisationResponseFactory#getSchemasRootPath()
     */
    @Override
    public String getSchemasRootPath() {
        return capabilitySchemasRootPath;
    }
}
